package service

import (
	"context"
	"fmt"
	"github.com/ecodeclub/ekit/slice"
	"golang.org/x/sync/errgroup"
	followv1 "jk-time/webook/api/proto/gen/follow/v1"
	"jk-time/webook/feed/domain"
	"jk-time/webook/feed/repository"
	"sort"
	"sync"
)

type feedService struct {
	repo         repository.FeedRepository
	handlerMap   map[string]Handler
	followClient followv1.FollowServiceClient
}

func NewFeedService(repo repository.FeedRepository, followClient followv1.FollowServiceClient, handlerMap map[string]Handler) FeedService {
	return &feedService{repo: repo, followClient: followClient, handlerMap: handlerMap}
}

func (f *feedService) RegisterService(typ string, handler Handler) {
	f.handlerMap[typ] = handler
}
func (s *feedService) CreateFeedEvent(ctx context.Context, feed domain.FeedEvent) error {
	handler, ok := s.handlerMap[feed.Type]
	if !ok {
		// 说明 type 不对
		// 你还可以考虑兜底机制
		// 有一个 defaultHandler，然后调用 defaultHandler
		return fmt.Errorf("未能找到对应的 Handler %s", feed.Type)
	}
	return handler.CreateHandleEvent(ctx, feed.Ext)
}

// GetFeedEventList 不依赖于Handler的直接查询
func (s *feedService) GetFeedEventListV1(ctx context.Context, uid, timestamp, limit int64) ([]domain.FeedEvent, error) {
	var (
		res  = make([]domain.FeedEvent, 0, limit*2)
		eg   errgroup.Group
		lock sync.Mutex
	)
	// 找被关注者发件箱
	eg.Go(func() error {
		// 是热点用户，直接从收件箱找
		if s.isActiveUser(uid) {
			return nil
		}
		resp, err := s.followClient.GetFollowee(ctx, &followv1.GetFolloweeRequest{
			Follower: uid,
			Limit:    100,
		})
		if err != nil {
			return err
		}
		followers := make([]int64, len(resp.FollowRelations))
		for i, fr := range resp.FollowRelations {
			followers[i] = fr.Follower
		}

		pulls, err := s.repo.FindPullEventListWithTyp(ctx, ArticleEventName, followers, timestamp, limit)
		if err != nil {
			return err
		}
		lock.Lock()
		res = append(res, pulls...)
		lock.Unlock()
		return nil
	})
	// 找自己收件箱
	eg.Go(func() error {
		pushs, err := s.repo.FindPushEventListWithTyp(ctx, ArticleEventName, uid, timestamp, limit)
		if err != nil {
			return err
		}
		lock.Lock()
		res = append(res, pushs...)
		lock.Unlock()
		return nil
	})

	err := eg.Wait()
	if err != nil {
		return nil, err
	}

	//排序
	sort.Slice(res, func(i, j int) bool {
		return res[i].Ctime.UnixMilli() > res[j].Ctime.UnixMilli()
	})
	return res[:slice.Min[int]([]int{int(limit), len(res)})], nil
}
func (s *feedService) GetFeedEventList(ctx context.Context, uid, timestamp,
	limit int64) ([]domain.FeedEvent, error) {
	var (
		res  = make([]domain.FeedEvent, 0, limit*int64(len(s.handlerMap)))
		eg   errgroup.Group
		lock sync.Mutex
	)
	for _, handler := range s.handlerMap {
		h := handler
		eg.Go(func() error {
			evts, err := h.FindHandleEvents(ctx, uid, timestamp, limit)
			if err != nil {
				return err
			}
			lock.Lock()
			res = append(res, evts...)
			lock.Unlock()
			return nil
		})
	}
	err := eg.Wait()
	if err != nil {
		return nil, err
	}
	// 你已经查询所有的数据，现在要排序
	sort.Slice(res, func(i, j int) bool {
		return res[i].Ctime.UnixMilli() > res[j].Ctime.UnixMilli()
	})
	return res[:slice.Min[int]([]int{int(limit), len(res)})], nil
}
func (f *feedService) isActiveUser(uid int64) bool {
	// 在实践中，是否是活跃用户，一般都是离线任务计算的。
	// 比如说每天计算一批，或者间隔一段时间计算一批
	// 可以考虑采用连续登录之类的方案
	// 而后在判定是否是活跃用户的时候，可以利用 redis 来实现 bit array 或者布隆过滤器
	// 并且，在布隆过滤器假阳性的情况下，你可以当成真的来处理
	// 在这种时候，你只会把一些不是活跃用户的判定为活跃用户，
	// 但是这种代价是可以接受的
	return false
}
